Skip to content

feat(gui): FreeDV Reporter window with live station table, band/freq filter, and track-slice mode#3439

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
NF0T:feat/freedv-reporter-window
Jun 7, 2026
Merged

feat(gui): FreeDV Reporter window with live station table, band/freq filter, and track-slice mode#3439
ten9876 merged 1 commit into
aethersdr:mainfrom
NF0T:feat/freedv-reporter-window

Conversation

@NF0T
Copy link
Copy Markdown
Collaborator

@NF0T NF0T commented Jun 6, 2026

Summary

Adds a FreeDV Reporter window (View menu → FreeDV Reporter) that shows the live station table from qso.freedv.org — the same data the existing spot source feeds into the DX cluster overlay, now surfaced in a dedicated resizable window.

FreeDvReporterDialog (src/gui/) — PersistentDialog subclass with:

  • QTableView driven by FreeDvReporterModel (13 columns: callsign, locator, distance km, bearing °, software version, frequency, mode, status, station message, last TX time, last heard callsign, SNR, last update)
  • Band filter row (160m / 80m / 40m / 20m / 15m / 10m / 6m / 2m / 70cm / All) — single-click, exclusive
  • Two filter modes: Band (by band edge) and Freq (±10 kHz of active slice)
  • Track active slice checkbox — auto-follows the slice frequency when Freq mode is active
  • Geometry and filter state persist via AppSettings nested-JSON (Principle V)
  • Frameless-chrome aware (FramelessWindowTitleBar + FramelessResizer::install)

FreeDvReporterModel (src/gui/) — QAbstractTableModel with:

  • Distance and bearing computed from operator grid (Maidenhead) via new MaidenheadLocator.h
  • Timed highlight system: RX events highlight for 5 s, message updates highlight for 5 s; TX status uses live info.status directly
  • m_highlightTimer ticks at 1 s to expire stale highlights without full-model resets

FreeDvClient extensions (src/core/):

  • StationInfo struct promoted from private to public — consumed directly by the model and dialog
  • New signals: stationUpdated(sid, info), stationRemoved(sid), stationsCleared()
  • m_inBulkUpdate flag prevents spot emission during bulk_update replay (spots only on live TX start)
  • onMessageUpdate() handler for message_update Socket.IO events (was silently ignored)
  • stationUpdated emitted from all event handlers so the model stays current without polling

MainWindow wiring: dialog constructed lazily on first open, wired to FreeDvClient signals and active-slice changes.

Constitution principle honored

Principle V — New Configuration Uses Nested JSON Per Feature, Not Flat AppSettings. All dialog settings (band filter index, filter mode, track-slice toggle, window geometry) are stored under a single AppSettings["FreeDvReporterDialog"] JSON blob.

Test plan

  • Build passes — MinGW GCC 13.1.0 / Qt 6.11.0, verified locally
  • Build passes with HAVE_WEBSOCKETS undefined — all new GUI code is gated #ifdef HAVE_WEBSOCKETS
  • FreeDV Reporter dialog opens from View menu and populates stations when connected to qso.freedv.org
  • Band filter correctly shows/hides stations by band edge
  • Freq filter + Track mode follows active slice frequency
  • TX/RX/highlight states update live during a FreeDV QSO
  • Window geometry and filter state persist across app restart
  • Existing tests pass (CI)

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls — nested-JSON under FreeDvReporterDialog (Principle V)
  • All meter UI uses MeterSmoother — N/A
  • Documentation updated if user-visible behavior changed — new View menu entry, no separate doc needed
  • Security-sensitive changes reference a GHSA if applicable — N/A

Note: src/gui/MainWindow.{h,cpp} are in the maintainer-only CODEOWNERS tier — this PR requires @ten9876 approval.

@NF0T NF0T requested review from a team as code owners June 6, 2026 19:41
@aethersdr-agent
Copy link
Copy Markdown
Contributor

Hi @NF0T — thanks for the FreeDV Reporter dialog, that's a great addition. 👋

I dug into the failing Qt Accessibility Static Analysis check on 12cb723b and the good news is it's a CI workflow bug, not anything in your code.

What I checked

Ran tools/check_a11y.py locally against the files this PR touches:

$ python3 tools/check_a11y.py \
    src/gui/FreeDvReporterDialog.cpp src/gui/FreeDvReporterDialog.h \
    src/gui/FreeDvReporterModel.cpp src/gui/FreeDvReporterModel.h \
    src/gui/MainWindow.cpp src/gui/MainWindow.h

=== AetherSDR accessibility check summary ===
Files scanned      : 6
Files with findings: 0
Total findings     : 0
No accessibility issues found.

Exit 0, zero findings. The "Comment with accessibility summary" sub-step on the failed run is also marked skipped (its if: is gated on findings != '0'), which confirms the linter agrees: nothing to flag here.

Why the step still failed

Bug is in .github/workflows/a11y-check.yml:

COUNT=$(grep -c "^::warning" /tmp/a11y_output.txt || echo "0")
echo "findings=$COUNT" >> $GITHUB_OUTPUT

When there are zero warnings, grep -c prints 0 and exits 1. The || echo "0" then runs and prints 0 again. Command substitution captures both, so $COUNT is the two-line string 0\n0 (verified locally: xxd shows 30 0a 30). The next line writes

findings=0
0

…to $GITHUB_OUTPUT. The runner reads findings=0 fine (which is why the comment step was correctly skipped), but the stray 0 line has no name=value separator and trips the runner's "Unable to process file command" validation, failing the step.

A one-liner fix would be COUNT=$(grep -c "^::warning" /tmp/a11y_output.txt || true)true produces no extra stdout, so $COUNT stays a clean 0. Worth a separate PR / issue against the workflow file (the tooling was added in a8bafc7 / #3289).

Bottom line for this PR

  • The a11y workflow comment explicitly says "Findings are warnings only — the build is not blocked" — this check isn't gating merge.
  • Your code passes the linter (0 findings).
  • The other jobs (build, check-macos, check-windows, analyze (cpp)) were still in progress when I looked — those are the ones to watch.

Nothing for you to change here. Sit tight on the other checks. 73 and thanks for contributing!


🤖 aethersdr-agent · cost: $22.2425 · model: claude-opus-4-7

…le. Principle V.

Non-modal resizable dialog under View → FreeDV Reporter showing all active
qso.freedv.org stations in a 13-column sortable table (Callsign, Locator,
km, Hdg, Version, MHz, TX Mode, Status, Message, Last TX, RX Call, SNR,
Last Update). Band/frequency filter with Track mode follows the active VFO
slice. Per-session sort on any column.

Row highlight palette matches freedv-gui reference:
- Orange (#fc4500): live TX — set/cleared exclusively by tx_report
- Teal (#379baf): RX receiving — 5 s timeout after rx_reports stop;
  cleared immediately when EOO callsign is decoded
- Purple (#e58be5): new station arrival or message update — 5 s timeout
- 250 ms QTimer drives expiry; black foreground on all highlights

State updates are decoupled from the SpotHub spot-emission gates so the
table reflects raw WebSocket protocol state, not the filtered spot feed.

Protocol correctness (Principle VIII — all verified against freedv-gui source):
- TX state owned exclusively by tx_report; freq_change never touches it
- bulk_update processes transmitting=false entries — no station stuck TX
- rx_only status immutable once set by new_connection
- Model reset on WebSocket disconnect prevents ghost rows on reconnect
- Dialog seeded from FreeDvClient cache on first open (lazy-construct safe)
- GPS-aware my-grid resolved at open time, not read from AppSettings directly

New files: src/core/MaidenheadLocator.h, src/gui/FreeDvReporterModel.h/.cpp,
           src/gui/FreeDvReporterDialog.h/.cpp
Modified:  src/core/FreeDvClient.h/.cpp, src/gui/MainWindow.h/.cpp,
           CMakeLists.txt

Principle V (config under AppSettings["FreeDvReporter"]).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ten9876 ten9876 force-pushed the feat/freedv-reporter-window branch from 12cb723 to 5e0e0d3 Compare June 6, 2026 22:58
@ten9876 ten9876 self-assigned this Jun 6, 2026
@ten9876
Copy link
Copy Markdown
Collaborator

ten9876 commented Jun 6, 2026

Rebased your branch onto current main (`5e0e0d32`) to pick up #3442's a11y workflow fix. Your code didn't trigger any a11y findings — the lint scanned 6 files and reported 0 findings, then the workflow step crashed writing the result to `$GITHUB_OUTPUT`.

What the failing run actually showed

```
=== AetherSDR accessibility check summary ===
Files scanned : 6
Files with findings: 0
Total findings : 0
No accessibility issues found.
##[error]Unable to process file command 'output' successfully.
##[error]Invalid format '0'
```

Root cause

The pre-#3442 workflow had:
```bash
COUNT=$(grep -c "^::warning" /tmp/a11y_output.txt || echo "0")
```

When `grep -c` finds zero matches it exits 1 and prints `0` to stdout, so `|| echo "0"` appended a second `0`, making `COUNT=$'0\\n0'`. The next line `echo "findings=$COUNT" >> $GITHUB_OUTPUT` wrote a two-line value that the GITHUB_OUTPUT parser rejected with `Invalid format '0'` — and the step failed even though the lint had already succeeded.

#3442 fixed this by changing `|| echo "0"` to `|| true`, so `COUNT` ends up as just `0` on the zero-findings case. Timing: your CI run was 2026-06-06 19:41 UTC, #3442 merged 2026-06-06 21:44 UTC — your run hit the workflow file from your branch (workflows run from PR-branch state on `pull_request` events), which was pre-fix.

What I did

Same maintainer-fix-up pattern as #3279/#3286/#3289/#3381/#3391/#3397/#3398/#3417 — rebased your single commit onto current main, no content changes. Verified locally on Arch Linux x86: full `AetherSDR` build clean (634/635, only the pre-existing unrelated `macDaxDriverInstalled` warning).

CI will rerun against the new HEAD. Happy to revert any piece you'd rather take a different cut at.

Principle XI.

ten9876 added a commit to ea5wa/AetherSDR that referenced this pull request Jun 6, 2026
Two related fixes to TitleBar's new PanLock accessors so all four platforms
compile again:

1. signals: vs. public: — MOC parses anything inside a `signals:` block as
   a signal declaration, so the inline `bool isPanFollowChecked() const`
   and `void setPanFollowChecked(bool)` were rejected at moc time with
   "Not a signal declaration" (TitleBar.h:56). Both methods are accessors,
   not signals — they belong in `public:`.

2. Out-of-line over inline. Even after the section move, the inline
   bodies referenced `m_panFollowBtn->isChecked()` and `QSignalBlocker`,
   which need the full QPushButton type. The header only forward-declares
   QPushButton, so inline bodies couldn't compile in callers either
   (the moc error masked this until now). Moving the definitions to
   TitleBar.cpp keeps the header light and matches the style of
   neighbours like `isSystemMoveAreaAt`.

Build verified locally on Arch Linux x86 — 632/632 clean (only the
pre-existing unrelated macDaxDriverInstalled warning). Same maintainer
fix-up pattern as aethersdr#3279/aethersdr#3286/aethersdr#3289/aethersdr#3381/aethersdr#3398/aethersdr#3417/aethersdr#3439/aethersdr#3441.

Principle XI.
@ten9876 ten9876 merged commit 8679cdb into aethersdr:main Jun 7, 2026
6 checks passed
ten9876 added a commit to ea5wa/AetherSDR that referenced this pull request Jun 7, 2026
Two related fixes to TitleBar's new PanLock accessors so all four platforms
compile again:

1. signals: vs. public: — MOC parses anything inside a `signals:` block as
   a signal declaration, so the inline `bool isPanFollowChecked() const`
   and `void setPanFollowChecked(bool)` were rejected at moc time with
   "Not a signal declaration" (TitleBar.h:56). Both methods are accessors,
   not signals — they belong in `public:`.

2. Out-of-line over inline. Even after the section move, the inline
   bodies referenced `m_panFollowBtn->isChecked()` and `QSignalBlocker`,
   which need the full QPushButton type. The header only forward-declares
   QPushButton, so inline bodies couldn't compile in callers either
   (the moc error masked this until now). Moving the definitions to
   TitleBar.cpp keeps the header light and matches the style of
   neighbours like `isSystemMoveAreaAt`.

Build verified locally on Arch Linux x86 — 632/632 clean (only the
pre-existing unrelated macDaxDriverInstalled warning). Same maintainer
fix-up pattern as aethersdr#3279/aethersdr#3286/aethersdr#3289/aethersdr#3381/aethersdr#3398/aethersdr#3417/aethersdr#3439/aethersdr#3441.

Principle XI.
@NF0T NF0T deleted the feat/freedv-reporter-window branch June 7, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants